package cn.nukkit.lang;

import cn.nukkit.Server;
import cn.nukkit.utils.Utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * author: MagicDroidX
 * Nukkit Project
 * 
 * nukkit 多语言支持的基础（语言名不区分大小写）。<br>
 * 默认的语言配置的位置在 lang/{langName}/lang.ini（请查看构造方法）<br> 
 * （如果想看结构的话，可以解压 nukkit 包，里面有预置的语言）
 * @author wanne (doc-api-chs )
 */
public class BaseLang {
	// 默认语言
    public static final String FALLBACK_LANGUAGE = "eng";

    // 启动时的语言（不区分大小写）
    protected final String langName;

    protected Map<String, String> lang = new HashMap<>();
    protected Map<String, String> fallbackLang = new HashMap<>();


    public BaseLang(String lang) {
        this(lang, null);
    }

    public BaseLang(String lang, String path) {
        this(lang, path, FALLBACK_LANGUAGE);
    }

    /** 如果path不为null，那么加载的文件文件位置为 {path}/{langName}/lang.ini <br>
     * 如果 path 为null，那么加载的文件位置为 lang/{langName}/lang.ini
     *  */
    public BaseLang(String lang, String path, String fallback) {
        this.langName = lang.toLowerCase();
        boolean useFallback = !lang.equals(fallback);

        if (path == null) {
            path = "lang/";
            this.lang = this.loadLang(this.getClass().getClassLoader().getResourceAsStream(path + this.langName + "/lang.ini"));
            if (useFallback) this.fallbackLang = this.loadLang(this.getClass().getClassLoader().getResourceAsStream(path + fallback + "/lang.ini"));
        } else {
            this.lang = this.loadLang(path + this.langName + "/lang.ini");
            if (useFallback) this.fallbackLang = this.loadLang(path + fallback + "/lang.ini");
        }
        if (this.fallbackLang == null) this.fallbackLang = this.lang;


    }

    public Map<String, String> getLangMap() {
        return lang;
    }

    public Map<String, String> getFallbackLangMap() {
        return fallbackLang;
    }

    public String getName() {
        return this.get("language.name");
    }

    public String getLang() {
        return langName;
    }

    /** 加载语言文件<br>
     * 由于使用的工具类在读文件时，使用了缓存，所以启动nukkit后，再次读取相同的语言文件并不是实时的文件<br><br>
     * （如果重构的话，可以用正则表达式）（不对呀，nukkit不是有自己的 {@link cn.nukkit.utils.Config} 类吗！）
     * <br><br>
     * 语言文件的规则和 properties 文件类似，但没有转义字符。
     *  ‘#’开头表示注释；‘=’连接键值对；每行只有一个键值对 ；只有第一个‘=’是连接键值的，其余的等号都是值里面的内容
     * */
    protected Map<String, String> loadLang(String path) {
        try {
            String content = Utils.readFile(path);
            Map<String, String> d = new HashMap<>();
            for (String line : content.split("\n")) {
                line = line.trim();
                if (line.equals("") || line.charAt(0) == '#') {
                    continue;
                }
                String[] t = line.split("=");
                if (t.length < 2) {
                    continue;
                }
                String key = t[0];
                String value = "";
                for (int i = 1; i < t.length - 1; i++) {
                    value += t[i] + "=";
                }
                value += t[t.length - 1];
                if (value.equals("")) {
                    continue;
                }
                d.put(key, value);
            }
            return d;
        } catch (IOException e) {
            Server.getInstance().getLogger().logException(e);
            return null;
        }
    }

    /** {@link #loadLang(String)} 不能加载语言文件更新后的内容，但这个可以 <br>
     * （我靠，这里面又把读取读取和分割键值对的代码写了一遍！）
     * */
    protected Map<String, String> loadLang(InputStream stream) {
        try {
            String content = Utils.readFile(stream);
            Map<String, String> d = new HashMap<>();
            for (String line : content.split("\n")) {
                line = line.trim();
                if (line.equals("") || line.charAt(0) == '#') {
                    continue;
                }
                String[] t = line.split("=");
                if (t.length < 2) {
                    continue;
                }
                String key = t[0];
                String value = "";
                for (int i = 1; i < t.length - 1; i++) {
                    value += t[i] + "=";
                }
                value += t[t.length - 1];
                if (value.equals("")) {
                    continue;
                }
                d.put(key, value);
            }
            return d;
        } catch (IOException e) {
            Server.getInstance().getLogger().logException(e);
            return null;
        }
    }

    public String translateString(String str) {
        return this.translateString(str, new String[]{}, null);
    }

    /** @see #translateString(String, Object...)*/
    public String translateString(String str, String... params) {
        if (params != null) {
            return this.translateString(str, params, null);
        }
        return this.translateString(str, new String[0], null);
    }

    /** 将预置的文字模板转换成最终的效果<br>
     * 例如：<br>
     * death.attack.player.item={%0} 被 {%1} 用 {%2} 杀死了
     * "death.attack.player.item" 是指某个玩家被另一个实体以某种方式杀死了，
     * 右边的模板字符串中的 "{%*}"就是要填充的内容，参数 params 就是用来填充的内容，
     * str 就是指文字模板的键 ""
     * @param str 文字模板的键 "death.attack.player.item"
     * @param params 填充数据，当对象不是String时，会调用其 toString()方法
     * @see #translateString(String, String[], String)
     * @return 最终的成品文字
     *  */
    public String translateString(String str, Object... params) {
        if (params != null) {
            String[] paramsToString = new String[params.length];
            for (int i = 0; i < params.length; i++) {
                paramsToString[i] = Objects.toString(params[i]);
            }
            return this.translateString(str, paramsToString, null);
        }
        return this.translateString(str, new String[0], null);
    }

    /** @see #translateString(String, String[], String)*/
    public String translateString(String str, String param, String onlyPrefix) {
        return this.translateString(str, new String[]{param}, onlyPrefix);
    }

    /** 把模板转换成成品文字*/
    public String translateString(String str, String[] params, String onlyPrefix) {
        String baseText = this.get(str);
        baseText = this.parseTranslation((baseText != null && (onlyPrefix == null || str.indexOf(onlyPrefix) == 0)) ? baseText : str, onlyPrefix);
        for (int i = 0; i < params.length; i++) {
            baseText = baseText.replace("{%" + i + "}", this.parseTranslation(String.valueOf(params[i])));
        }

        return baseText;
    }

    /** 把模板转换成成品文字*/
    public String translate(TextContainer c) {
        String baseText = this.parseTranslation(c.getText());
        if (c instanceof TranslationContainer) {
            baseText = this.internalGet(c.getText());
            baseText = this.parseTranslation(baseText != null ? baseText : c.getText());
            for (int i = 0; i < ((TranslationContainer) c).getParameters().length; i++) {
                baseText = baseText.replace("{%" + i + "}", this.parseTranslation(((TranslationContainer) c).getParameters()[i]));
            }
        }
        return baseText;
    }

    /** 通过键获取到模板内容， 没有时会返回 null*/
    public String internalGet(String id) {
        if (this.lang.containsKey(id)) {
            return this.lang.get(id);
        } else if (this.fallbackLang.containsKey(id)) {
            return this.fallbackLang.get(id);
        }
        return null;
    }

    /** 通过键获取到模板内容， 没有时，直接返回键*/
    public String get(String id) {
        if (this.lang.containsKey(id)) {
            return this.lang.get(id);
        } else if (this.fallbackLang.containsKey(id)) {
            return this.fallbackLang.get(id);
        }
        return id;
    }

    /** @see #parseTranslation(String, String)*/
    protected String parseTranslation(String text) {
        return this.parseTranslation(text, null);
    }

    /** 对模板进行预处理。如果一个模板中用了引入了另一个模板，就会在这里进行替换（nukkit 并没有这样做）。<br>
     * death.attack.player.item={%0} 被 {%1} 用 {%2} 杀死了<br>
     * <br>
     * 模板文字定义规则:
     * {%?} ?部分由 数字、字母、'.' 和 '-' 组成，
     * 并且 ? 可以是其它模板的键，也就是说 允许模板嵌套。
     * */
    protected String parseTranslation(String text, String onlyPrefix) {
        String newString = "";
        text = String.valueOf(text);

        String replaceString = null;

        int len = text.length();

        for (int i = 0; i < len; ++i) {
            char c = text.charAt(i);
            if (replaceString != null) {
                int ord = c;
                if ((ord >= 0x30 && ord <= 0x39) // 0-9
                        || (ord >= 0x41 && ord <= 0x5a) // A-Z
                        || (ord >= 0x61 && ord <= 0x7a) || // a-z
                        c == '.' || c == '-') {
                    replaceString += String.valueOf(c);
                } else {
                	// 如果 replacString 是语言文件中的键，那么查出来的就不是 null，同时指定的前缀就是其它模板的键时（不含），
                	// 直接用其它键的模板文字部分替换当前这部分。
                    String t = this.internalGet(replaceString.substring(1));
                    if (t != null && (onlyPrefix == null || replaceString.indexOf(onlyPrefix) == 1)) {
                        newString += t;
                    } else {
                        newString += replaceString;
                    }
                    replaceString = null;
                    if (c == '%') {
                        replaceString = String.valueOf(c);
                    } else {
                        newString += String.valueOf(c);
                    }
                }
            } else if (c == '%') {
                // 当查到以 % 字符时，确认为 {%?} 部分，replacString 就是要确定的?部分，replacString 是 % 开头的
            	replaceString = String.valueOf(c);
            } else {
                newString += String.valueOf(c);
            }
        }

        if (replaceString != null) {
            String t = this.internalGet(replaceString.substring(1));
            if (t != null && (onlyPrefix == null || replaceString.indexOf(onlyPrefix) == 1)) {
                newString += t;
            } else {
                newString += replaceString;
            }
        }
        return newString;
    }
}
